<template>
  <article class="page page-img-diagram">
    <h4 class="text-center">图片变黑白线稿</h4>
    <form class="form">
      <div class="form-group">
        <fieldset>
          <legend>参数设置</legend>
          <div class="row row-flex">
            <div class="col col-6">
              <div class="row-flex" align="center">
                <div class="row-flex-prefix">模糊半径</div>
                <div class="row-flex-main">
                  <input class="form-control" type="number" :min="0" v-model.trim="blurRadius" placeholder="请输入模糊半径" />
                </div>
              </div>
            </div>
            <div class="col col-6">
              <div class="row-flex" align="center">
                <div class="row-flex-prefix">阈值范围</div>
                <div class="row-flex-main">
                  <input class="form-control" type="number" :min="0" :max="255" v-model.trim="lowThreshold" placeholder="低阈值" />
                </div>
                <div class="row-flex-main">
                  <input class="form-control" type="number" :min="0" :max="255" v-model.trim="highThreshold" placeholder="高阈值" />
                </div>
              </div>
            </div>
          </div>
          <div class="row-flex" align="center">
            <div class="row-flex-prefix">边长上限</div>
            <div class="row-flex-main">
              <input class="form-control" type="number" :min="1" v-model.trim="maxSize" placeholder="请输入边长上限，不输入则使用图片原始尺寸" />
            </div>
          </div>
          <div class="row-flex" align="center">
            <div class="row-flex-prefix">下载位置</div>
            <div class="row-flex-main"><input class="form-control" type="text" v-model="toPath" /></div>
            <div class="row-flex-suffix"><x-button @click="setToPath">选择...</x-button></div>
          </div>
        </fieldset>
      </div>
      <div class="form-group form-group-main row">
        <div class="col col-9">
            <fieldset>
                <legend>图片列表</legend>
                <div class="fieldset-content">
                  <div class="table-header" :style="thStyles">
                    <table>
                      <tbody>
                        <tr>
                          <th v-for="(v,i) in tableHeaders" :key="`th_${i}`" :style="{width: v.width}">{{v.label}}</th>
                        </tr>
                      </tbody>
                    </table>
                  </div>
                  <div class="table-body" @drop.prevent="filesDrop" @dragover.prevent>
                    <table ref="table">
                      <tbody>
                        <tr v-for="(v,i) in imagesList" :key="`file_${i}`" :class="{active: i === imageActiveIndex}" @click="clickImageItem(i)">
                          <td v-for="(item,index) in tableHeaders" :key="`td_${i}_${index}`" :style="{width: item.width}">
                            <div v-if="item.value==='status'">{{imageStatusList[v.status]}}</div>
                            <div v-else-if="item.value==='action'">
                              <button v-if="v.canvas" type="button" class="btn btn-link btn-sm" @click.stop="downloadImageItem(i)">下载</button>
                              <button v-else type="button" class="btn btn-link btn-sm" @click.stop="getDiagramImageItem(v,i)">转换</button>
                              <button type="button" class="btn btn-link btn-sm" @click.stop="deleteImageItem(i)">删除</button>
                            </div>
                            <div v-else>{{v[item.value] || ''}}</div>
                          </td>
                        </tr>
                        <tr v-if="imagesList.length===0" class="nodata-tr"><td>将待处理的文件拖动到此处</td></tr>
                      </tbody>
                    </table>
                  </div>
                </div>
            </fieldset>
        </div>
        <div class="col col-3">
            <fieldset>
                <legend>图片预览</legend>
                <div class="fieldset-content preview">
                    <img class="preview-img" :src="imagePreviewInfo.src1 || imagePreviewInfo.src" />
                    <div class="preview-name text-ellipsis">{{imagePreviewInfo.name}}</div>
                </div>
            </fieldset>
        </div>
      </div>
      <div class="form-group text-center">
        <x-button type="default" @click="clearImagesList">清空列表</x-button>
        <x-button type="primary" @click="getDiagramAll" :loading="loadingCount > 0">线图转换</x-button>
        <x-button type="primary" @click="downloadAll" :loading="loadingCount > 0">全部下载</x-button>
      </div>
      <div v-if="loadingCount > 0" class="mask">
        <x-loading></x-loading>
      </div>
    </form>
  </article>
</template>

<script>
const path = global.require('path')
const {remote} = global.require('electron')
const {dialog} = remote
const selfWindow= remote.getCurrentWindow()
let jsfeat = global.jsfeat || {}

export default {
  name: 'img-diagram',
  data () {
    return {
      blurRadius: 2, // 模糊半径
      lowThreshold: 20,
      highThreshold: 50,
      maxSize: 512,
      toPath: this.$sharedObject('desktopPath'),
      imagesList: [],
      imageActiveIndex: -1,
      imageStatusList: ['未处理', '正在处理', '已处理'],
      tableHeaders: [
        {label: '文件名', value: 'name', width: '20%'},
        {label: '路径', value: 'path', width: '50%'},
        {label: '状态', value: 'status', width: '10%'},
        {label: '操作', value: 'action', width: '20%'},
      ],
      thStyles: {},
      loadingCount: 0
    }
  },
  computed: {
    imagePreviewInfo () {
      let imageActive = this.imagesList[this.imageActiveIndex]
      return imageActive || {}
    }
  },
  methods: {
    // 设置下载位置
    setToPath () {
      let filePaths = dialog.showOpenDialogSync(selfWindow, {
        title: '选择目录',
        buttonLabel:'确定',
        defaultPath: this.$sharedObject('desktopPath'),
        properties: ['openDirectory']
      })
      if (filePaths instanceof Array && filePaths.length > 0) {
        this.toPath = filePaths[0]
      }
    },
    // 拖拽添加文件
    filesDrop (e) {
      const fileList = e.dataTransfer.files
      if (!fileList) return
      let taskList = []
      let indexList = []
      for (let i = 0; i < fileList.length; i++) {
        const file = fileList[i]
        if (!file.type || !/.(gif|jpg|jpeg|png)$/i.test(file.name)) {
          continue
        }
        taskList.push(this.$jstools.loadImage(file))
        indexList.push(i)
      }
      this.loadingCount++
      Promise.all(taskList).then((imgList) => {
        imgList.forEach((v, i) => {
          let file = fileList[indexList[i]]
          this.imagesList.push({
            name: file.name, path: file.path, src: v.src, src1: '', imgObj: v || null, canvas: null, status: 0
          })
        })
        this.checkScroll()
      }).finally(() => {
        this.loadingCount--
      })
    },
    // 点击图片
    clickImageItem (index) {
      if (this.imageActiveIndex === index) {
        this.imageActiveIndex = -1
      } else {
        this.imageActiveIndex = index
      }
    },
    // 删除图片
    deleteImageItem (index) {
      this.imagesList.splice(index, 1)
      if (this.imageActiveIndex === index) {
        this.imageActiveIndex = -1
      } else if (this.imageActiveIndex > index) {
        this.imageActiveIndex -= 1
      }
    },
    // 清空列表
    clearImagesList () {
      this.imagesList = []
      this.imageActiveIndex = -1
      this.thStyles = {}
    },
    // 线图转换
    getDiagramAll () {
      if (this.imagesList.length <= 0) {
        return this.$toast('未找到可处理的文件')
      }
      this.loadingCount++
      setTimeout(() => {
        let taskList = []
        this.imagesList.forEach((v, i) => {
          taskList.push(this.getDiagramItem(v, i))
        })
        Promise.all(taskList).finally(() => {
          this.loadingCount--
          this.$toast('处理完成')
          this.$forceUpdate()
        })
      }, 100)
    },
    getDiagramImageItem (item, index) {
      this.loadingCount++
      this.getDiagramItem(item, index).finally(() => {
        this.loadingCount--
        this.$toast('处理完成')
      })
    },
    getDiagramItem (item, index) {
      if (!item || !item.imgObj) return Promise.reject('无效数据')
      this.imagesList[index] && this.$set(this.imagesList[index], 'status', 1)
      // 可选参数
      let blurRadius = this.blurRadius || 2
      let lowThreshold = Math.min(this.lowThreshold, this.highThreshold) || 20
      let highThreshold = Math.max(this.lowThreshold, this.highThreshold) || 50
      // 图片原始尺寸
      let source = item.imgObj
      let originWidth = source.naturalWidth || source.width
      let originHeight = source.naturalHeight || source.height
      // 最大尺寸限制
      // let maxWidth = 512, maxHeight = 512
      let maxWidth = parseFloat(this.maxSize), maxHeight = parseFloat(this.maxSize)
      // 目标尺寸
      let width = originWidth, height = originHeight
      if (this.maxSize) {
        // 图片尺寸超过限制
        if (originWidth > maxWidth || originHeight > maxHeight) {
          if (originWidth / originHeight > maxWidth / maxHeight) {
            width = maxWidth
            height = Math.round(maxWidth * (originHeight / originWidth))
          } else {
            height = maxHeight
            width = Math.round(maxHeight * (originWidth / originHeight))
          }
        }
      }
      // canvas
      let canvas = document.createElement('canvas')
      let context = canvas.getContext('2d')
      canvas.width = source.width = width
      canvas.height = source.height = height
      // 清除内容
      context.clearRect(0, 0, width, height)
      context.drawImage(source, 0, 0, width, height)
      let imageData = context.getImageData(0, 0, width, height)
      // 图像边缘查找处理，原理为：1. 灰度 2. 高斯模糊 3. canny边缘检测
      let img_u8 = new jsfeat.matrix_t(width, height, jsfeat.U8C1_t)
      jsfeat.imgproc.grayscale(imageData.data, width, height, img_u8)
      let kernelSize = (blurRadius + 1) * 2
      jsfeat.imgproc.gaussian_blur(img_u8, img_u8, kernelSize, 0)
      jsfeat.imgproc.canny(img_u8, img_u8, lowThreshold, highThreshold)
      // 渲染结果重新绘制到canvas
      let data_u32 = new Uint32Array(imageData.data.buffer)
      let alpha = (0xff << 24)
      let i = img_u8.cols * img_u8.rows, pix = 0
      while(--i >= 0) {
        pix = 255 - img_u8.data[i]
        data_u32[i] = alpha | (pix << 16) | (pix << 8) | pix
      }
      context.putImageData(imageData, 0, 0)
      if (this.imagesList[index]) {
        this.imagesList[index].canvas = canvas
        this.imagesList[index].src1 = canvas.toDataURL('image/png')
        this.$set(this.imagesList[index], 'status', 2)
      }
      return Promise.resolve()
    },
    // 全部下载
    downloadAll () {
      if (!this.toPath) {
        return this.$toast('请设置文件下载位置')
      }
      if (this.imagesList.length <= 0) {
        return this.$toast('未找到可下载的图片')
      }
      let taskList = []
      this.imagesList.forEach((v, i) => {
        if (v.status === 2) {
          taskList.push(this.downloadItem(i))
        }
      })
      if (taskList.length <= 0) {
        return this.$toast('未找到可下载的图片')
      }
      this.loadingCount++
      Promise.all(taskList).finally(() => {
        this.loadingCount--
        this.$toast('下载完成')
      })
    },
    downloadImageItem (index) {
      if (!this.toPath) {
        return this.$toast('请设置文件下载位置')
      }
      this.loadingCount++
      this.downloadItem(index).finally(() => {
        this.loadingCount--
        this.$toast('下载完成')
      })
    },
    downloadItem (index) {
      let item = this.imagesList[index] || {}
      let fileName = `line_${item.name}`
      let filePath = path.join(this.toPath, fileName)
      if (!item.canvas) return Promise.reject('无数据')
      return new Promise((resolve, reject) => {
        item.canvas.toBlob((blob) => {
          if (!blob) {return reject('无数据')}
          blob.arrayBuffer().then((res) => {
            this.$sharedObject('jstoolsNode').writeFile(filePath, Buffer.from(res))
            resolve()
          }).catch((err) => {
            reject('发生错误：' + err)
          })
        }, item.mimeType || 'image/png')
      })
    },
    checkScroll () {
      this.$nextTick(() => {
        if (!this.$refs.table) return
        let tableHeight = this.$refs.table.clientHeight
        let tableParentHeight = this.$refs.table.parentNode.clientHeight
        if (tableHeight > tableParentHeight) {
          this.thStyles = {overflow: 'hidden scroll'}
        } else {
          this.thStyles = {overflow: 'hidden'}
        }
      })
    },
  }
}
</script>

<style scoped lang="less">
@import "~@/assets/css/_mixins.less";
@import "~@/assets/css/_color.less";
.row-flex {
  & + .row-flex { margin-top: .5em; }
  .row-flex-prefix { width: 4.5em; text-align: left; }
}
.form {
  height: calc(100vh - 137px);
  display: flex;
  flex-direction: column;
  position: relative;
  .form-group {
    fieldset {
        height: 100%;
        .fieldset-content {
            height: calc(100% - 2em);
            display: flex;
            flex-direction: column;

            .table-header,
            .table-body {
              table {
                width: 100%;
                border: 1px solid @borderGrey;
                tr {height: 2em;}
                th, td {
                  border: 1px solid @borderGrey;
                  text-align: center;
                  vertical-align: middle;
                }
                .nodata-tr {
                  height: 100%;
                  font-size: 16px;
                  color: #999;
                }
              }
            }
            .table-body {
              height: calc(100% - 1.5em);
              overflow: auto;
              table {
                min-height: 100%;
                tr {cursor: pointer;}
                tr.active { background-color: @bgGrey; }
                .td-content {
                  padding: .1em .5em;
                }
              }
            }
            .preview-img {
              display: block;
              width: 100%;
              height: 100%;
              object-fit: contain;
            }
            .preview-img-8 {
              position: absolute;
              top: 2px;
              right: 2px;
              width: 50px;
              height: 50px;
              border: 2px dashed @colorPrimary;
              object-fit: contain;
              background-color: rgba(255, 255, 255, .5);
            }
            .preview-name {
              position: absolute;
              bottom: 0;
              left: 0;
              width: 100%;
              height: 2em;
              line-height: 2;
              color: #fff;
              text-align: center;
              background-color: rgba(0, 0, 0, .3);
            }
        }
        .preview {
          border-radius: .5em;
          background-color: @bgGrey;
          overflow: hidden;
          position: relative;
        }
    }
  }
  .form-group-main {
    height: 0;
    flex-grow: 1;
    & > .col {height: 100%;}
  }
  .mask {
    .absolute-fullscreen;
    z-index: 2;
    border-radius: 8px;
    background-color: rgba(255,255,255,.5);
    .flex-center;
  }
}
</style>
